﻿// <copyright file="CongressApi.cs" company="Microsoft Corporation">
// Copyright (c) 2009 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2009-04-28</date>
// <summary>CongressApi class definition</summary>

namespace NYTimes.Core
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Net;
    using System.Windows;
    using System.Windows.Media.Imaging;
    using System.Xml.Linq;
    using Synergist;

    /// <summary>
    /// New York Times Congress API
    /// </summary>
    /// <remarks>See <a href="http://developer.nytimes.com/docs/congress_api" target="_blank">this 
    /// article</a> for more information about the New York Times Congress API.
    /// <list type="table">
    /// <listheader>
    /// <term>Rate Limiting</term>
    /// </listheader>
    ///     <item><term>Default Queries Per Second</term><description>2</description></item>
    ///     <item><term>Resource Key</term><description>NYTCongressQPS</description></item>
    ///     <item><term>Overriding default QPS</term><description>To Override the rate limiting add 
    ///     a namespace declaration <c>xmlns:sys="clr-namespace:System;assembly=mscorlib"</c> and 
    ///     an integer resource to the App.Xaml Application.Resources like this: 
    ///     <c>&lt;sys:Int32 x:Key="NYTCongressQPS"&gt;4&lt;/sys:Int32&gt;</c></description>
    ///     </item>
    /// </list>
    /// </remarks>
    public abstract class CongressApi : INotifyPropertyChanged
    {
        #region Fields
        /// <summary>
        /// Name of key in application resources
        /// </summary>
        private const string CongressApiKey = "NYTCongressKey";

        /// <summary>
        /// 2 QPS rate limiting
        /// </summary>
        private static RateLimiting limiting = new RateLimiting("NYTCongress", 2);

        /// <summary>
        /// the New York Times Congress API Key
        /// </summary>
        private string apiKey;

        /// <summary>
        /// the status returned from the service
        /// </summary>
        private string status;

        /// <summary>
        /// the copyright info
        /// </summary>
        private string copyright;

        /// <summary>
        /// the web client
        /// </summary>
        private WebClient client;

        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the CongressApi class.
        /// </summary>
        protected CongressApi()
        {
            this.Version = "v3";

            if (Application.Current.Resources.Contains(CongressApiKey))
            {
                // we set the field, not the property here
                this.apiKey = Application.Current.Resources[CongressApiKey] as string;
            }
        }
        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Property changed event handler
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the API Key
        /// </summary>
        [Description("the New York Times Congress API Key")]
        public string ApiKey 
        { 
            get
            {
                return this.apiKey;
            }

            set
            {
                if (this.apiKey != value)
                {
                    this.apiKey = value;

                    this.FetchResults();
                }
            }
        }

        /// <summary>
        /// Gets the logo for the New York Times
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1822", Justification = "This is exposed to a Silverlight application.")]
        public BitmapImage Logo
        {
            get
            {
                var streamResource = Application.GetResourceStream(
    new Uri("NYTimesSilverlightKit;component/Images/Logo.png", UriKind.Relative));

                var image = new BitmapImage();

#if SILVERLIGHT
                image.SetSource(streamResource.Stream);
#else
                image.StreamSource = streamResource.Stream;
#endif

                return image;
            }
        }

        /// <summary>
        /// Gets The API Version
        /// </summary>
        public string Version { get; private set; }

        /// <summary>
        /// Gets the web service result status
        /// </summary>
        public string Status
        {
            get
            {
                return this.status;
            }

            private set
            {
                if (this.status != value)
                {
                    this.status = value;

                    this.TriggerPropertyChanged("Status");
                }
            }
        }

        /// <summary>
        /// Gets the web service result copyright message
        /// </summary>
        public string Copyright
        {
            get
            {
                return this.copyright;
            }

            private set
            {
                if (this.copyright != value)
                {
                    this.copyright = value;

                    this.TriggerPropertyChanged("Copyright");
                }
            }
        }

        /// <summary>
        /// Gets the rate limiting
        /// </summary>
        protected static RateLimiting Limiting
        {
            get
            {
                return limiting;
            }
        }

        /// <summary>
        /// Gets the base URI for all service calls
        /// </summary>
        private string BaseUri
        {
            get
            {
                return string.Format(CultureInfo.InvariantCulture, "http://api.nytimes.com/svc/politics/{0}/us/legislative/congress", this.Version);
            }
        }
        #endregion

        #region
        /// <summary>
        /// Parse the results
        /// </summary>
        /// <param name="results">the XML results</param>
        public abstract void OnParseResults(XElement results);

        /// <summary>
        /// determines whether the state is valid to fetch results
        /// </summary>
        /// <returns>true to fetch results, false otherwise</returns>
        public abstract bool ShouldFetchResults();

        /// <summary>
        /// Clear the results
        /// </summary>
        public abstract void ClearResults();

        /// <summary>
        /// Generate the service URL
        /// </summary>
        /// <param name="baseUri">the Base URL</param>
        /// <returns>the service URL</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "this will always be used as a string.")]
        public abstract Uri GenerateUrl(string baseUri);
        #endregion

        #region Implementation
        /// <summary>
        /// Fetch the result set
        /// </summary>
        protected void FetchResults()
        {
            if (SampleData.IsInDesignTool)
            {
                return;
            }

            if (string.IsNullOrEmpty(this.ApiKey))
            {
                return;
            }

            if (!this.ShouldFetchResults())
            {
                return;
            }

            this.Status = string.Empty;

            this.ClearResults();

            if (this.client != null)
            {
                this.client.CancelAsync();

                Limiting.Cancel(this.client);
            }

            var uri = this.GenerateUrl(this.BaseUri);

            this.client = new WebClient();

            this.client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnDownloadStringCompleted);

            Limiting.MakeWebRequest(this.client, uri);
        }

        /// <summary>
        /// Trigger a property changed event
        /// </summary>
        /// <param name="propertyName">the property name</param>
        protected void TriggerPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Parse the XML returned from the service
        /// </summary>
        /// <param name="xml">the XML string</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Set the status to the exception message.")]
        protected void ParseXml(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                return;
            }

            try
            {
                var doc = XDocument.Parse(xml);

                var resultSet = doc.Element("result_set");

                var results = resultSet.Element("results");

                this.OnParseResults(results);

                this.Copyright = resultSet.Element("copyright").Value;
                this.Status = resultSet.Element("status").Value;
            }
            catch (System.Exception se)
            {
                this.Status = se.Message;
            }
        }

        /// <summary>
        /// Download string completed event handler
        /// </summary>
        /// <param name="sender">the web client</param>
        /// <param name="e">the download string completed event arguments</param>
        private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                return;
            }

            this.client = null;

            if (e.Error != null)
            {
                System.Diagnostics.Debug.WriteLine("Error downloading string for Congress API: {0}", e.Error.Message);

                WebException we = e.Error as WebException;

                if (we != null)
                {
                    this.Status = we.Status.ToString();
                }
                else
                {
                    this.Status = e.Error.Message;
                }

                return;
            }

            var xml = e.Result;

            this.ParseXml(xml);
        }

        #endregion
    }
}
